﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Estructuras.Comunes;

namespace Estructuras.Funciones.Analizar
{
    public static class AnalizarDatos
    {
        /// <summary>
        /// Analiza los datos de entrada en base a las tablas de definición de datos y registros.
        /// </summary>
        /// <param name="entrada">Fichero de datos de entrada</param>
        /// <param name="salida">Fichero de datos de salida</param>
        /// <param name="salidaPalabrasNoEncontradas">Fichero de salida que contendrá los datos no encontrados</param>
        /// <param name="salidaPatronesNoEncontrados">Fichero de salida que contendrá los patrones no encontrados</param>
        /// <param name="listaValores">Lista de valores en los que buscar los datos</param>
        /// <param name="listaMascaras">Lista de máscaras en las que buscar las máscaras de los datos</param>
        /// <param name="listaPatronesAlias">Lista de patrones de registros en la que buscar los datos analizados</param>
        /// <param name="diccionarioAliasCampos">Relación de alias y campo asociados</param>
        /// <param name="camposAnalizar">Lista de campos a analizar</param>
        /// <param name="separadores">Lista de caracteres que serán considerados como separadores</param>
        /// <param name="caracteresEspeciales">Lista de caracteres especiales definidos por el usuario</param>
        /// <param name="caracteresEliminarInicio">Lista de datos a eliminar antes de comenzar el análisis</param>
        /// <param name="caracteresModificarInicio">Lista de datos a modificar antes de comenzar el análisis</param>
        /// <param name="formatoEntrada">Estructura de los datos de entrada</param>
        /// <param name="formatoSalida">Estrucutra de los datos de salida</param>
        /// <returns>Número de registros procesados, número de datos no encontrados y número de patrones no encontrados</returns>
        public static List<long> Analizar (TextReader entrada, TextWriter salida, TextWriter salidaPalabrasNoEncontradas, TextWriter salidaPatronesNoEncontrados,
            Dictionary<string, Valor> listaValores, Dictionary<string, Mascara> listaMascaras, Dictionary<string, Dictionary<string, List<string>>> listaPatronesAlias, 
            Dictionary<string, string> diccionarioAliasCampos, List<string> camposAnalizar,
            List<char> separadores, List<char> caracteresEspeciales, List<string> caracteresEliminarInicio, Dictionary<string, string> caracteresModificarInicio,
            Estructura.Estructura formatoEntrada, Estructura.Estructura formatoSalida)
        {
            try
            {
                List<long> filasProcesadas = new List<long>();
                filasProcesadas.Add(0);
                string linea = entrada.ReadLine();
                ObtenerTags analisis = new ObtenerTags(listaValores, listaMascaras, separadores, caracteresEspeciales);
                PDatosNoEncontrados pDatosNoEncontrados = new PDatosNoEncontrados();
                PRegistrosNoEncontrados pRegistrosNoEncontrados = new PRegistrosNoEncontrados();
                while (linea != null)
                {
                    string registroSalida = formatoSalida.nuevoRegistro(linea, formatoEntrada);
                    // Por cada campo a analizar analiza el contenido del mismo
                    foreach (string campo in camposAnalizar)
                    {
                        string datoAnalizar = formatoEntrada.obtenerValorCampo(linea, campo);

                        DatosNoEncontrados datosNoEncontrados = new DatosNoEncontrados();
                        datosNoEncontrados.Campo = campo;
                        datosNoEncontrados.RegistroOriginal = datoAnalizar;

                        // Preprocesado datos
                        datoAnalizar = PreprocesadoDatos(datoAnalizar, caracteresEliminarInicio, caracteresModificarInicio);

                        // Análisis de los datos
                        ArbolBinario<ValorSalida> arbol = null;
                        analisis.Analizar(datoAnalizar, ref arbol);

                        datosNoEncontrados.ListaDatos = new List<string>(analisis.DatosNoAsignados);
                        analisis.reiniciarListaDatosNoEncontrados();

                        // Forma el patrón de salida
                        List<ValorSalida> listaNodos = new List<ValorSalida>();
                        listaNodos = ArbolBinario<ValorSalida>.Listar(arbol, listaNodos);

                        // Crea la lista de nodos únicos
                        string patronAlterado = string.Empty;
                        Dictionary<string, ValorSalida> DiccionarioNodos = PreprocesadoPatron(listaNodos, ref patronAlterado);

                        datosNoEncontrados.PatronRegistro = patronAlterado;

                        // Busca el patrón obtenido
                        if (!listaPatronesAlias.ContainsKey(patronAlterado))
                        {
                            // No se encuentra
                            RegistroNoEncontrado reg = new RegistroNoEncontrado();
                            reg.Campo = campo;
                            reg.RegistroOriginal = datoAnalizar;
                            reg.PatronRegistro = patronAlterado;
                            pRegistrosNoEncontrados.ListaRegistros.Add(reg);
                        }
                        else
                        {
                            // Patrón encontrado
                            // Por cada alias asociado al patrón, crea la salida
                            Dictionary<string, string> aliasOtrosTags = new Dictionary<string,string>();
                            foreach (string alias in listaPatronesAlias[patronAlterado].Keys)
                            {
                                if (diccionarioAliasCampos.ContainsKey(alias))
                                {
                                    string valorSalida = FormarSalida(DiccionarioNodos, listaPatronesAlias[patronAlterado][alias], ref aliasOtrosTags);
                                    formatoSalida.establecerValorCampo(ref registroSalida, diccionarioAliasCampos[alias], valorSalida);
                                }
                            }
                            // Por cada "otro tag" sobreescribe la salida
                            foreach (string alias in aliasOtrosTags.Keys)
                            {
                                if (diccionarioAliasCampos.ContainsKey(alias))
                                    formatoSalida.establecerValorCampo(ref registroSalida, diccionarioAliasCampos[alias], aliasOtrosTags[alias]);
                            }
                        }

                        // Añade los datos no encontrados si existen
                        if (datosNoEncontrados.ListaDatos.Count != 0)
                            pDatosNoEncontrados.ListaDatos.Add(datosNoEncontrados);
                    }
                    salida.WriteLine(registroSalida);
                    filasProcesadas[0]++;
                    linea = entrada.ReadLine();
                }
                // Escribe los datos no encontrados
                filasProcesadas.Add(pDatosNoEncontrados.ListaDatos.Count);
                filasProcesadas.Add(pRegistrosNoEncontrados.ListaRegistros.Count);
                EscribirPalabrasNoEncontradas(salidaPalabrasNoEncontradas, pDatosNoEncontrados);
                EscribirPatronesNoEncontrados(salidaPatronesNoEncontrados, pRegistrosNoEncontrados);
                return filasProcesadas;
            }
            catch (Excepcion ex)
            {
                throw ex;
            }
            catch (Exception ex)
            {
                throw new Excepcion(Constantes.ErrorAnalizarDatos + ex.Message, Constantes.CodigoErrorAnalizarDatos);
            }
        }

        /// <summary>
        /// Escribe los datos no encontrados en la salida indicada.
        /// </summary>
        /// <param name="salida">Fichero de salida</param>
        /// <param name="palabrasNoEncontradas">Lista de datos no encontrados</param>
        private static void EscribirPalabrasNoEncontradas(TextWriter salida, PDatosNoEncontrados palabrasNoEncontradas)
        {
            palabrasNoEncontradas.escribir(salida);
        }

        /// <summary>
        /// Escribe los regitros no encontrados en la salida indicada.
        /// </summary>
        /// <param name="salida">Fichero de salida</param>
        /// <param name="registrosNoEncontrados">Lista de registros no encontrados</param>
        private static void EscribirPatronesNoEncontrados(TextWriter salida, PRegistrosNoEncontrados registrosNoEncontrados)
        {
            registrosNoEncontrados.escribir(salida);
        }

        /// <summary>
        /// Preprocesa los datos a analizar.
        /// </summary>
        /// <param name="dato">Dato a analizar</param>
        /// <param name="eliminar">Elementos a eliminar del dato</param>
        /// <param name="modificar">Elementos a modificar del dato</param>
        /// <returns>Dato modificado</returns>
        private static string PreprocesadoDatos(string dato, List<string> eliminar, Dictionary<string, string> modificar)
        {
            string datoPreprocesado = dato.ToUpper();
            foreach (string elemento in eliminar)
                datoPreprocesado = datoPreprocesado.Replace(elemento, string.Empty);
            foreach (string elemento in modificar.Keys)
                datoPreprocesado = datoPreprocesado.Replace(elemento, modificar[elemento]);
            return datoPreprocesado;
        }

        /// <summary>
        /// Procesa el patrón obtenido al analizar los datos, incluyendo un ordinal al nombre del tag.
        /// </summary>
        /// <param name="lista">Lista de datos analizados</param>
        /// <param name="patronAlterado">Patrón de salida formado con los tags modificados</param>
        /// <returns>Lista de nodos únicos con el tag asociado</returns>
        private static Dictionary<string, ValorSalida> PreprocesadoPatron(List<ValorSalida> lista, ref string patronAlterado)
        {
            patronAlterado = string.Empty;
            Dictionary<string, int> indiceTag = new Dictionary<string, int>();
            Dictionary<string, int> indiceTagTotal = new Dictionary<string, int>();
            Dictionary<string, ValorSalida> nuevaLista = new Dictionary<string, ValorSalida>();
            for(int indice = 0; indice < lista.Count; indice++)
            {
                // Busca si nodo actual puede encontrarse con otro igual
                int indiceInterior = 0;
                bool encontrado = false;
                while (!encontrado && indiceInterior < lista.Count)
                {
                    if (indiceInterior != indice)
                    {
                        if (lista[indice].Tag.Equals(lista[indiceInterior].Tag) &&
                            lista[indice].Salida.Equals(lista[indiceInterior].Salida))
                        {
                            encontrado = true;
                        }
                    }
                    indiceInterior++;
                }

                // Si se encuentra
                if (encontrado)
                {
                    if (!indiceTagTotal.ContainsKey(lista[indice].Tag))
                        indiceTagTotal.Add(lista[indice].Tag, 0);

                    if (!indiceTag.ContainsKey(lista[indice].Tag + " - " + lista[indice].Salida))
                        indiceTag.Add(lista[indice].Tag + " - " + lista[indice].Salida, indiceTagTotal[lista[indice].Tag]);

                    patronAlterado += lista[indice].Tag + "_" + indiceTag[lista[indice].Tag + " - " + lista[indice].Salida].ToString() + " ";
                    if (!nuevaLista.ContainsKey(lista[indice].Tag + "_" + indiceTag[lista[indice].Tag + " - " + lista[indice].Salida].ToString()))
                        nuevaLista.Add(lista[indice].Tag + "_" + indiceTag[lista[indice].Tag + " - " + lista[indice].Salida].ToString(), lista[indice]);
                    indiceTagTotal[lista[indice].Tag]++;
                }
                else
                {
                    // Si no se encuentra repetido
                    if (!indiceTagTotal.ContainsKey(lista[indice].Tag))
                        indiceTagTotal.Add(lista[indice].Tag, 0);
                    
                    patronAlterado += lista[indice].Tag + "_" + indiceTagTotal[lista[indice].Tag].ToString() + " ";
                    if (!nuevaLista.ContainsKey(lista[indice].Tag + "_" + indiceTagTotal[lista[indice].Tag].ToString()))
                        nuevaLista.Add(lista[indice].Tag + "_" + indiceTagTotal[lista[indice].Tag].ToString(), lista[indice]);
                    indiceTagTotal[lista[indice].Tag]++;
                }
            }
            patronAlterado = patronAlterado.Substring(0, patronAlterado.Length - 1);
            return nuevaLista;
        }

        /// <summary>
        /// Crea el dato de salida en función de la lista de datos a unir.
        /// </summary>
        /// <param name="tagsInternos">Listado de valores detectados internamente</param>
        /// <param name="tagsSalida">Listado de tags que conforman la salida</param>
        /// <param name="valoresAliasOtrosTags">Listado de valores a incluir en los alias por otros tags</param>
        /// <returns>Dato de salida</returns>
        private static string FormarSalida(Dictionary<string, ValorSalida> tagsInternos, List<string> tagsSalida, ref Dictionary<string, string> valoresAliasOtrosTags)
        {
            string salida = string.Empty;
            foreach (string tag in tagsSalida)
            {
                if (tagsInternos.ContainsKey(tag))
                {
                    salida += tagsInternos[tag].Salida + " ";
                    foreach(string alias in tagsInternos[tag].OtrosTags.Keys)
                    {
                        if (!valoresAliasOtrosTags.ContainsKey(alias))
                            valoresAliasOtrosTags.Add(alias, tagsInternos[tag].OtrosTags[alias]);
                    }
                }
            }
            return salida;
        }
    }
}
